Skip to content

feat(harbor): task.yaml/experiment.yaml → Harbor export + coder-eval as a Harbor agent - #166

Merged
akshaylive merged 14 commits into
mainfrom
akshaya/harbor_experiment_export
Sep 12, 2026
Merged

feat(harbor): task.yaml/experiment.yaml → Harbor export + coder-eval as a Harbor agent#166
akshaylive merged 14 commits into
mainfrom
akshaya/harbor_experiment_export

Conversation

@akshaylive

Copy link
Copy Markdown
Collaborator

Summary

Adds a Harbor-interop layer for coder-eval:

  • Export (coder-eval export --format harbor): converts a coder-eval task.yaml (optionally × experiment.yaml variants) into a Harbor task directory, using coder-eval's own criteria as the grader.
  • coder-eval as a Harbor agent (CoderEvalAgent, -a coder_eval.harbor.agent:CoderEvalAgent): runs coder-eval execute --format harbor inside a Harbor trial container, in-place at the container's own WORKDIR (--workspace-dir), and hands Harbor back a real ATIF trajectory.json.
  • Supporting fixes found only by running the whole pipeline against a real harbor==0.22.0 install, real Docker, and real Bedrock credentials: [environment.env]/[verifier.env] auto-derived from sandbox.docker.env_passthrough (${VAR:-} templates so Harbor doesn't hard-require every credential var), an always-written environment/Dockerfile, a fixed placeholder instruction.md, template_sources directories copied into the export with rewritten paths, and TaskDefinition no longer requiring a placeholder success criterion.
  • A new Harbor E2E workflow (.github/workflows/harbor-e2e.yml) that exports and runs 4 real scenarios (plain docker driver, llm_judge, custom BYOD image, template_sources) end-to-end and asserts reward.json == 1.0 + trajectory.json + task.json presence.

Test plan

  • make format && make check && make lint clean
  • make typecheck at the known 9-error baseline (pre-existing optional-extra imports, unchanged)
  • make test — 5069 passed, same 10 pre-existing unrelated litellm-extra failures
  • All 4 Harbor E2E scenarios verified locally end-to-end against real Docker + real Bedrock credentials (reward 1.0, no exceptions, for each)
  • harbor-e2e.yml triggered via workflow_dispatch on this PR's branch to confirm it passes in CI too (not triggered by pull_request itself — see the workflow's own comment)

🤖 Generated with Claude Code

akshaylive and others added 7 commits September 10, 2026 12:07
…he grader

Implements C0/C1.1/C1.4/C2/C3(golden) of the Harbor interop plan
(tmp/harborframework.md): `coder-eval export --format harbor <task.yaml> -o
<dir>` emits a Harbor task directory whose `tests/test.sh` grades with
coder-eval's own criteria via a two-line verifier shim.

- `coder_eval.harbor.reward` — translates a graded run's task.json into
  Harbor's reward.json contract. A missing/unmeasured weighted_score writes
  no reward file (never coalesced to 0.0), so Harbor's own
  RewardFileNotFoundError path masks the trial instead of scoring it zero.
- `coder_eval.harbor.portability` — classifies all 15 criterion types so an
  unsupported criterion is refused at export time, not as a mystery low
  reward at verify time. Registry-derived coverage over the real
  SuccessCriterion union.
- `coder_eval.harbor.packager` — the export writer itself: task.toml,
  instruction.md, environment/, tests/test.sh, tests/task.yaml,
  tests/reference/. New dependency: tomli-w.
- `coder-eval harbor reward` / `coder-eval export` CLI commands.
- CE004 widened to scope harbor/ as a core layer (must not import
  coder_eval.cli).

Verified live against real Harbor 0.22.0 + real docker (not just unit
tested): exported tasks scored reward 1.0 (correct solution), 0.5 (wrong
content, genuinely measured not masked), and correctly masked with no
solution present. Two bugs the live run found and fixed that no unit test
caught: test.sh referenced tests/task.yaml as cwd-relative when Harbor
uploads the whole tests/ tree to the absolute /tests/; and `set -e` in
test.sh was silently discarding every real failing score as if it were
unmeasured, since `coder-eval evaluate` exits non-zero on a failed
criterion (a measured verdict, not an infra failure).

v1 assumes the exported task's Dockerfile is FROM coder-eval-agent:<tag>
(confirmed live) so the verifier's `coder-eval` binary is present; the
packager warns, non-blocking, when it can't confirm that.

Full design, verification log and known follow-up (C1.2/C1.3, solution/
auto-derivation, network:none, docs) recorded in tmp/harborframework.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ng /app

Converting a real in-tree task (hello_date_smoke_test) through the export
pipeline surfaced a live bug in the bare-`docker.image` branch of
`_write_environment`: it defaulted `workdir` to the hardcoded
`DEFAULT_WORKDIR = "/app"` whenever `sandbox.docker.working_dir` was unset,
regardless of the image's actual WORKDIR.

`coder-eval-agent:latest`'s real WORKDIR is `/work`. Harbor's shared verifier
runs `docker exec -w <workdir> <container> ...`, and confirmed live that
`docker exec -w` -- unlike `docker run -w`, which auto-creates the directory
-- hard-fails to chdir into a path that doesn't already exist in the image
("no such file or directory"), surfacing as agent exit 127 with no verifier
output at all. The dockerfile_path branch never had this bug (it appends a
WORKDIR line and the directory exists once Docker builds it); the bare-image
branch had no Dockerfile to fix and no way to introspect the image, so it
just guessed.

Fix: `_inspect_image_workdir()` runs `docker image inspect <image> --format
'{{.Config.WorkingDir}}'` (best-effort, 30s timeout, returns None on any
failure) and the bare-image branch now tries, in order: explicit
`working_dir` -> inspected WORKDIR -> `DEFAULT_WORKDIR`, warning
(non-blocking) only on the last fallback.

Re-verified live after the fix: `harbor run -p <dir> -a oracle` against the
converted hello_date task scored reward: 1.0.

Test hygiene: added an autouse fixture stubbing `_inspect_image_workdir`
across `test_harbor_packager.py` so packager unit tests never depend on
what happens to be cached in the local docker daemon -- a real image named
`byod-custom-image:0.1.0` (this file's own placeholder name), built by an
unrelated docker-integration test elsewhere in the suite, silently flipped
a DEFAULT_WORKDIR assertion the first time this ran locally. New tests
(TestPrebuiltImageWorkdirInspection) cover the inspection helper directly
and its three-way fallback order.

Full narrative in tmp/harborframework.md (gitignored), including a scoping
note for the next piece (coder-eval as Harbor's *agent*, not just its
verifier) -- not started, planning only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extends the single-task Harbor exporter to experiment.yaml: each resolved
(task, variant, replicate[, dataset row]) combination gets its own Harbor
directory, since Harbor's task.toml has no variant concept. Reuses
resolve_all_tasks (the same pipeline `run -e` uses) for resolution, and
skips (rather than silently drops) a variant whose agent/simulation
override Harbor's verifier-only export cannot honor, detected precisely
via config_lineage so a task's own agent config never false-triggers it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adopts ATIF (Agent Trajectory Interchange Format) as coder-eval's
trajectory interchange layer so a Harbor trial can run coder-eval itself
as the agent, not just as the grader.

- harbor/atif_models.py + atif_emit.py: vendored ATIF schema + a pure
  EvaluationResult -> Trajectory converter, opt-in via `--format harbor`
  on `execute`/`run` (writes a trajectory.json sibling to task.json;
  never changes default output).
- harbor/atif_hydrate.py: the reverse direction (Trajectory -> TurnRecords)
  so `evaluate --format harbor --trajectory <path>` can grade
  trajectory-dependent criteria against an ATIF file that didn't
  originate from an in-process run.
- harbor/agent.py: CoderEvalAgent(BaseInstalledAgent) — a real Harbor
  agent that shells out to `coder-eval execute --format harbor` inside
  the container and lets Harbor discover the resulting trajectory.json
  via `populate_context_post_run`, matching Harbor's own ClaudeCode
  agent's exact convention. Verified end-to-end against a real
  harbor==0.22.0 install: a full export -> docker build -> `harbor run`
  -> grade round trip produces correct rewards for both a passing and a
  deliberately failing criterion.
- packager.py: bakes a criteria-free agent-phase task.yaml into the
  image at a fixed path (agent_paths.py) via a Dockerfile COPY line, so
  CoderEvalAgent always has a task to execute without ever seeing the
  real success_criteria.
- run_command.py: when `--format harbor` writes exactly one trajectory
  (the shape a CoderEvalAgent invocation always produces), it is also
  copied to <run_dir>/trajectory.json so a fixed-path caller (Harbor's
  agent log dir) can find it without knowing coder-eval's internal
  variant/task/replicate nesting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…plate sources

Several fixes to the Harbor export/agent pipeline, each verified against a
real `harbor==0.22.0` install with real Docker and (where relevant) real
Bedrock credentials:

- CoderEvalAgent now passes --workspace-dir "$(pwd)" to `coder-eval execute`,
  and BatchRunConfig/run_batch/run+execute CLIs gained a matching
  --workspace-dir flag (Orchestrator already supported this internally for
  DockerRunner). Without it, the agent phase wrote to a throwaway tempdir the
  verifier phase never looked at, silently scoring every criterion 0.
- packager.py now emits [environment.env]/[verifier.env] in task.toml, derived
  from the resolved task's own sandbox.docker.env_passthrough (the same
  allowlist DockerRunner already uses) rather than requiring the operator to
  hand-edit task.toml. HOME is excluded (that entry only makes sense paired
  with DockerRunner's ~/.claude bind-mount). Values are `${VAR:-}` templates
  (empty default) so Harbor doesn't hard-require every var to be present.
- environment/Dockerfile is now always written (synthesized from
  sandbox.docker.image when no dockerfile_path is set), so the CoderEvalAgent
  embed always has somewhere to COPY task.yaml into.
- instruction.md is now a fixed placeholder pointing at environment/task.yaml
  and the CoderEvalAgent agent, instead of leaking the real prompt to a
  stock Harbor agent that isn't the one this export is built for.
- environment/task.yaml's success_criteria is now a real `[]` instead of a
  placeholder criterion, now that TaskDefinition.validate_success_criteria
  (the "at least one criterion" check) has been removed.
- TemplateDirSource directories are now copied into
  environment/templates/<n>-<name>/ with their path rewritten to the
  in-container copy; environment/task.yaml's sandbox block is field-merged
  (driver forced to tempdir) instead of being replaced wholesale, so
  python.env_packages/limits/etc. survive.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Installs harbor==0.22.0 alongside coder-eval in the same venv, builds the
coder-eval-agent and byod-custom-image Docker images, then exports and runs
four scenarios via `hb run -a coder_eval.harbor.agent:CoderEvalAgent`:
baseline (plain docker driver, default image), llm_judge (real judge call
inside the verifier phase), docker_custom_image (reuses tasks/byod_smoke_test.yaml's
dockerfile_path + custom image), and template_sources (TemplateDirSource
copy-in). Each scenario asserts reward.json == 1.0, a real ATIF
trajectory.json, and both agent- and verifier-side task.json.

Verified locally end-to-end against real Docker + real Bedrock credentials
before committing (all four scenarios: reward 1.0, no exceptions).

Triggered on workflow_dispatch/schedule/push-to-main only (not pull_request)
so it's informational rather than a required PR check for now — it depends
on live model calls and Docker builds, and Harbor's own release cadence is
independent of this repo's.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread src/coder_eval/harbor/agent.py Fixed
Comment thread tests/test_atif_emit.py Fixed
@UiPath UiPath deleted a comment from github-actions Bot Sep 11, 2026
akshaylive and others added 4 commits September 11, 2026 11:32
…kage

`harbor` is intentionally not a project dependency -- coder_eval.harbor.agent
only ever runs inside a Harbor trial container, where it's already present
(see the module's own ImportError message). CI's Quality Gate installs the
codex/litellm extras, which cleared those pre-existing pyright errors, so
these 3 harbor.* import errors were the only real failures blocking PR #166 --
scoped `# pyright: ignore[reportMissingImports]` on the same 3 lines, matching
the existing pattern for openai_codex's optional import in codex_agent.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two more CI-only failures found running PR #166's full Quality
Gate/Windows Smoke Test jobs (masked previously by the typecheck failure
that aborted before the test step ran):

- test_execute_format_harbor.py / test_evaluate_format_harbor.py assert a
  plain substring ("Unsupported --format", "--trajectory") against a raised
  Click/Typer error's rendered output. CI renders that error box with color,
  which splits "--format"/"--trajectory" across ANSI escape sequences and
  silently breaks a naive substring check -- reproduced locally with
  FORCE_COLOR=1. Fixed with click.unstyle(result.output) before asserting.
- Two harbor packager tests assert the exported test.sh's chmod executable
  bit, which is meaningless on NTFS (Windows Smoke Test runs on Windows).
  Skipped/guarded the same way test_sandbox.py already does for other
  POSIX-only behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes surfaced by /coder-eval-code-review-full over two iterations:

- Refuse a task with empty success_criteria under `run`/`evaluate`
  (grade=True) -- it previously loaded and would silently score
  FinalStatus.SUCCESS at weighted_score 0.0 since
  validate_success_criteria was removed to let the criteria-free
  Harbor agent-phase task.yaml round-trip through `execute`.
  `execute` (grade=False) still accepts it. Scoped to the
  post-`--resume` task set so an already-finalized row is never
  blocked.
- Refuse a path-traversal-shaped variant_id/task_id/row_id in
  experiment export (`_out_subdir`) instead of silently writing
  outside `-o`'s output directory.
- Convert `--workspace-dir` misuse (docker driver, >1 task) from an
  unhandled ValueError/traceback into a clean typer.BadParameter.
- Escalate a missing `template_sources` directory during export from
  a warning to a hard failure -- the agent-phase task.yaml still
  references it, so a silently-skipped copy shipped an export with no
  starter code.
- Add a `harbor` optional extra (pinned harbor==0.22.0) so
  harbor/agent.py type-checks against real types instead of scoped
  pyright ignores; wire it into the two CI jobs that run pyright and
  simplify harbor-e2e.yml's install step to use it.
- New test coverage: harbor/agent.py's run()/populate_context_post_run
  (previously zero coverage), the empty-criteria and workspace_dir
  guards, the path-traversal refusal, and the template-source warn vs.
  hard-fail branches.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…utput

CI renders Click's error box with color, splitting "--workspace-dir"
across escape sequences and breaking the new
test_workspace_dir_with_docker_driver_is_a_clean_cli_error test that
raw-substring-matched it. Same fix as the existing --format/--trajectory
tests: assert against click.unstyle(result.output) instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
uipreliga

This comment was marked as outdated.

@uipreliga

Copy link
Copy Markdown
Collaborator

Let's see if they like it... i thought that the easiest approach would have been to simplify the tasks, remove some of the criteria while move others to the testing scripts. If some tasks are not possible to score in Harbor, we shouldn't just give them out.

@uipreliga
uipreliga self-requested a review September 11, 2026 23:21

@uipreliga uipreliga left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚢

akshaylive and others added 3 commits September 11, 2026 16:39
…rops)

Closes out the reviewer's Top-5 priority actions from the full 8-axis
review of this branch, plus the two CodeQL findings:

- Restore the empty-`success_criteria` refusal at the layers `run
  --to_run`'s guard didn't cover: `regrade_in_place` (shared by `evaluate
  <run_dir>` and `run --resume`'s `to_grade` set), the orchestrator-direct
  `evaluate` branch, and the resume `to_grade` partition itself. Without
  this, a criteria-less task graded SUCCESS at weighted_score 0.0 and
  exited 0.
- Widen `write_reward`'s unmeasured-row guard to also refuse
  `final_status.category == "error"`, since a grading-time infra crash
  (JudgeInfrastructureError/CheckerMisuseError/ReferenceTamperedError)
  finalizes ERROR with weighted_score 0.0, not None -- publishing a crash
  to Harbor as a measured zero.
- Read `result_status`/`duration_ms` back out of ATIF's ToolCall/
  ObservationResult `extra` in atif_hydrate, so `command_executed
  (require_success: true)` no longer silently scores 0.0 on the
  `evaluate --format harbor` path for a command that succeeded.
- Replace the two hand-picked export payloads: carry `run_limits` (both
  agent-phase and verifier task.yaml) and `checker_context` (verifier)
  through instead of dropping them, and refuse exporting a `dataset:`-
  backed task (unexpanded `${row.*}` placeholders) or an enabled
  `simulation:` block.
- `_unhonorable_override_reason` no longer skips agent-override variants:
  CoderEvalAgent (C1.2) now carries `task.agent` verbatim and executes it,
  so `export -e experiment.yaml` exports one directory per variant instead
  of exiting with zero.
- shlex.quote the task-YAML-controlled `working_dir` interpolated into the
  generated Harbor verifier `tests/test.sh` (command injection via a
  crafted `sandbox.docker.working_dir`).
- Drop symlinks (never dereference) when copying template/reference trees
  into an export, matching every other task-authored-tree copy in `src/`;
  wrap the reference copy's OSError as TaskNotExportableError.
- `--` before the task-controlled image name in `docker image inspect`.
- Fix the SubagentTrajectoryRef vendored ATIF model to match harbor==0.22.0
  exactly (verified against the installed package): all four fields
  optional, `session_id` added -- the old required-`trajectory_id` shape
  rejected two of harbor's own valid document forms.
- `evaluate --format harbor`'s write-back now checks `target.mode is
  RUN_DIR`, not just `prior is not None`: a synthetic prior seeded from
  `--trajectory` on the WORK_DIR shape was planting a spurious task.json
  into the Harbor-synced work dir.
- Narrow the DIRECT_WRITE stale-artifact warning suppression for
  `workspace_dir` to `IN_CONTAINER_ENV` (CE056) instead of the field
  itself: `--workspace-dir` is now also a host-reachable CLI flag, where
  the named directory persists across runs exactly like DIRECT_WRITE's
  own target.
- Remove an unnecessary `del environment` (CodeQL) and fix a test's
  dual import style (`import X.Y` + `from X.Y import Z`, CodeQL) for the
  same module.

Regenerated the golden export fixture for the `run_limits` addition.
Full suite green (5706 passed), ruff/pyright/lint clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets the same per-task agent image double as a Harbor agent image
(coder_eval.harbor.agent:CoderEvalAgent, a
harbor.agents.installed.base.BaseInstalledAgent subclass) without a
separate build, matching the harbor extra already wired into both
pyright CI jobs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@akshaylive
akshaylive merged commit 9caffcb into main Sep 12, 2026
15 checks passed
@akshaylive
akshaylive deleted the akshaya/harbor_experiment_export branch September 12, 2026 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants